﻿using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection.Extensions;
using CNative.Cloud.CPlatform;
using CNative.Cloud.CPlatform.Engines;
using CNative.Cloud.CPlatform.Module;
using CNative.Cloud.CPlatform.Runtime.Server;
using CNative.Cloud.CPlatform.Serialization;
using CNative.Cloud.KestrelHttpServer.Extensions;
using CNative.Cloud.KestrelHttpServer.Internal; 
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading.Tasks;
using CNative.Cloud.CPlatform.Routing;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using CNative.Cloud.KestrelHttpServer.Filters;
using CNative.Cloud.CPlatform.Messages;
using System.Diagnostics;
using CNative.Cloud.CPlatform.Configurations;
using CNative.Cloud.CPlatform.Diagnostics;
using CNative.Cloud.CPlatform.Utilities;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Http;
using CNative.Cloud.CPlatform.Exceptions;

namespace CNative.Cloud.KestrelHttpServer
{
    public class KestrelHttpMessageListener : HttpMessageListener, IDisposable
    {
        private readonly ILogger<KestrelHttpMessageListener> _logger;
#if NETCOREAPP3_1 || NET6_0
        private IHost _host;
        private readonly IHostApplicationLifetime _lifetime;
#else
        private IWebHost _host;
        private readonly IServiceEngineLifetime _lifetime;
#endif
        private bool _isCompleted;
        private readonly ISerializer<string> _serializer;
        private readonly IModuleProvider _moduleProvider;
        private readonly CPlatformContainer _container;
        private readonly IServiceRouteProvider _serviceRouteProvider;
        private readonly DiagnosticListener _diagnosticListener;
        private readonly ContainerBuilder _containerBuilder;

        public KestrelHttpMessageListener(ILogger<KestrelHttpMessageListener> logger,
            ISerializer<string> serializer,
#if NETCOREAPP3_1 || NET6_0
        IHostApplicationLifetime lifetime,
#else       
        IServiceEngineLifetime lifetime,
#endif
        IModuleProvider moduleProvider,
            IServiceRouteProvider serviceRouteProvider,
            CPlatformContainer container,
            ContainerBuilder containerBuilder) : base(logger, serializer, serviceRouteProvider)
        {
            _logger = logger;
            _serializer = serializer;
            _lifetime = lifetime;
            _moduleProvider = moduleProvider;
            _container = container;
            _serviceRouteProvider = serviceRouteProvider;
            _containerBuilder = containerBuilder;
            _diagnosticListener = new DiagnosticListener(DiagnosticListenerExtensions.DiagnosticListenerName);
        }

        public async Task StartAsync(IPAddress address,int? port)
        { 
            try
            {
                _logger.LogInformation($"Kestrel StartAsync...");

                if (AppConfig.ServerOptions.DockerDeployMode == DockerDeployMode.Swarm)
                {
                    address = IPAddress.Any;
                }
#if NETCOREAPP3_1 || NET6_0
                var hostBuilder = Host.CreateDefaultBuilder()
                       .ConfigureWebHostDefaults(config =>
                       {
                           config.ConfigureServices(ConfigureServices)
                               .Configure(AppResolve)
                               .ConfigureKestrel((context, options) =>
                               {
                                   options.Limits.MinRequestBodyDataRate = null;
                                   options.Limits.MinResponseDataRate = null;
                                   options.Limits.MaxRequestBodySize = null;
                                   options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(30);
                                   if (port != null && port > 0)
                                   {
                                       options.Listen(address, port.Value, listenOptions =>
                                       {
                                           listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
                                       });
                                   }
                                   ConfigureHost(context, options, address);
                               })
                               ;
                           if (Directory.Exists(AppConfig.ServerOptions.WebRootPath))
                               config.UseWebRoot(AppConfig.ServerOptions.WebRootPath);
                       })
                   ;

                _host = hostBuilder.Build();

                _lifetime.ApplicationStarted.Register(async () =>
#else
                var hostBuilder = new WebHostBuilder()
                  .UseContentRoot(Directory.GetCurrentDirectory())
                  .ConfigureServices(ConfigureServices)
                  .Configure(AppResolve)
                  .ConfigureKestrel((context, options) =>
                  {
                      options.Limits.MinRequestBodyDataRate = null;
                      options.Limits.MinResponseDataRate = null;
                      options.Limits.MaxRequestBodySize = null;
                      options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(30);
                      if (port != null && port > 0)
                      {
                          options.Listen(address, port.Value, listenOptions =>
                          {
                              listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
                          });
                      }
                      ConfigureHost(context, options, address);
                  })
                  .ConfigureLogging((logger) =>
                  {
                      logger.AddConfiguration(
                             CPlatform.AppConfig.GetSection("Logging"));
                  })
                  .UseKestrel()
                  ;

                if (Directory.Exists(CPlatform.AppConfig.ServerOptions.WebRootPath))
                    hostBuilder = hostBuilder.UseWebRoot(CPlatform.AppConfig.ServerOptions.WebRootPath);

                _host = hostBuilder.Build();
                
                _lifetime.ServiceEngineStarted.Register(async () =>
#endif
                {
                    if (_moduleProvider.Modules.Any(p => p.ModuleName == "SwaggerModule" && p.Enable))
                    {
                        var httpProtocol = GetHttpProtocol();
                        _logger.LogInformation($"Kestrel主机将启动,Swagger文档地址为:{httpProtocol}://{address}:{port}/swagger/index.html");
                    }
                    else
                    {
                        _logger.LogInformation($"Kestrel主机即将启动");
                    }
                    await _host.RunAsync();
                });
            }
            catch(Exception ex)
            {
                _logger.LogError($"http服务主机启动失败，监听地址：{address}:{port}。 \n{ex.GetExceptionMessage()}");
            }

        }
        private string GetHttpProtocol()
        {
            var httpProtocol = "http";
            if (_moduleProvider.Modules.Any(p => p.ModuleName == "StageModule"))
            {
                var stageModule = _moduleProvider.Modules.First(p => p.ModuleName == "StageModule" && p.Enable);
                var enableHttpsObj = stageModule.GetType().GetProperty("EnableHttps")?.GetValue(stageModule);
                if (enableHttpsObj != null)
                {
                    httpProtocol = Convert.ToBoolean(enableHttpsObj) ? "https" : "http";
                }
            }

            return httpProtocol;
        }
        public void ConfigureHost(WebHostBuilderContext context, KestrelServerOptions options,IPAddress ipAddress)
        {
            _moduleProvider.ConfigureHost(new WebHostContext(context, options, ipAddress));
        }

        public void ConfigureServices(IServiceCollection services)
        {
            //var builder = new ContainerBuilder();
#if NETCOREAPP3_1 || NET6_0
            services.AddControllers();
#else
            services.AddMvc();
#endif
            services.AddCors(options =>
            {
                options.AddPolicy("any", policyBuilder =>
                {
                    policyBuilder.WithOrigins("*");
                    policyBuilder.AllowAnyHeader();
                    policyBuilder.AllowAnyMethod();
                    policyBuilder.AllowAnyOrigin();
                    //policyBuilder.AllowCredentials();
                });
            });

            _moduleProvider.ConfigureServices(new ConfigurationContext(services,
                _moduleProvider.Modules, _moduleProvider.VirtualPaths, AppConfig.Configuration));

            //builder.Populate(services, _containerBuilder);
            _containerBuilder.Populate(services, _container);
        }

        private void AppResolve(IApplicationBuilder app)
        {
#if NETCOREAPP3_1 || NET6_0
            app.UseDeveloperExceptionPage();
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
#else
            app.UseStaticFiles();
            app.UseMvc();
#endif
            app.UseCors("any");

            _moduleProvider.Initialize(new ApplicationInitializationContext(app, _moduleProvider.Modules,
                _moduleProvider.VirtualPaths, AppConfig.Configuration));

            app.Run(async (context) =>
            {
                var messageId = Guid.NewGuid().ToString("N");
                var sender = new HttpServerMessageSender(_serializer, context,_diagnosticListener);
                try
                {
                    var filters = app.ApplicationServices.GetServices<IAuthorizationFilter>();
                    var isSuccess = await OnAuthorization(context, sender, messageId, filters);
                    if (isSuccess)
                    {
                        var actionFilters = app.ApplicationServices.GetServices<IActionFilter>();
                        await OnReceived(sender, messageId, context, actionFilters);
                    }
                }
                catch (Exception ex)
                {
                    var filters = app.ApplicationServices.GetServices<IExceptionFilter>();
                    WirteDiagnosticError(messageId, ex);
                    await OnException(context, sender, messageId, ex, filters);
                }
            });
        }

        private void WirteDiagnosticError(string messageId,Exception ex)
        {
            _diagnosticListener.WriteTransportError(CPlatform.Diagnostics.TransportType.Rest, new TransportErrorEventData(new DiagnosticMessage
            {
                Id = messageId
            }, ex));
        }

        public void Dispose()
        {
            _host?.Dispose();
        }

    }
}
